Developer Documentation

QuickTime 4 API Documentation

Inside Macintosh: QuickTime

Previous | Overview | Contents | Next |

A Sample Program for Compressing and Decompressing a Sequence of Images

The sample program presented in this section illustrates the processes described in the previous sections. The program has been divided into several functions. Listing 3-2 shows the main program.

Listing 2Compressing and decompressing a sequence of images: The main program

WindowPtr       displayWindow;          /* window in which to display
                                             sequence */
Rect            windowRect; /* rectangle of displayWindow */

main (void)
{
    WindowPtr       displayWindow;
    Rect            windowRect;
    
    InitGraf (&thePort);
    InitFonts ();
    InitWindows ();
    InitMenus ();
    TEInit ();
    InitDialogs (nil);
    
    SetRect (&windowRect, 0, 0, 256, 256);
    OffsetRect (&windowRect,/* middle of screen */
        ((qd.screenBits.bounds.right - qd.screenBits.bounds.left) -
                 windowRect.right) / 2,
        ((qd.screenBits.bounds.bottom - qd.screenBits.bounds.top) -
                 windowRect.bottom) / 2);
    displayWindow = NewCWindow (nil, &windowRect,
                                            "\pImage", true, 0,
                                            (WindowPtr)-1, true, 0);
    if (displayWindow)
    {
        SetPort (displayWindow);
        SequenceSave ();
        SequencePlay ();
    }
}

A Sample Function for Saving a Sequence of Images to a Disk File

The SequenceSave function shown in Listing 3 saves a sequence of images to a disk file. This function creates and opens a disk file for the image sequence, calls the CompressSequence function to create and compress the image sequence into the file, and then calls the MakeMyResource function to save the image description resource in the file, so that the sequence can be played back later. For details on CompressSequence , see the next section.

The data for each frame is written to the data fork of the disk file, preceded by a long word that contains the number of bytes of data for that frame. A description of the compressed images in the sequence is stored in a 'SEQU' resource in the same file with a resource ID of 128 or 129. This description is simply the image description structure maintained by the Image Compression Manager.

The image for each frame of the sequence is drawn into an offscreen graphics world that the SequenceSave function creates in the currWorld variable. SequenceSave calls the DrawOneFrame function (described in the next section) to draw each frame's image into the currWorld variable. Before any of the frames of the sequence are drawn, the Image Compression Manager is prepared to compress a sequence of images through the CompressSequence function.

Compressing and decompressing a sequence of images: Saving a sequence to a disk file

void SequenceSave (void)
{
    long                            filePos;
    StandardFileReply               fileReply;
    short                           dfRef = 0;
    OSErr                           error;          
    ImageDescriptionHandle          description = nil;

    StandardPutFile ("\p", "\pSequence File", &fileReply);
    if (fileReply.sfGood)
    {
        if (! (fileReply.sfReplacing))
        {
            error = FSpCreate (&fileReply.sfFile, 'SEQM', 'SEQU',
                                     fileReply.sfScript);
            CheckError (error, "\pFSpCreate");
        }
        error = FSpOpenDF (&fileReply.sfFile, fsWrPerm, &dfRef);
        CheckError (error, "\pFSpOpenDF");
        
        error = SetFPos (dfRef, fsFromStart, 0);
        CheckError (error, "\pSetFPos");
        
        CompressSequence (&dfRef, &description);
        error = GetFPos (dfRef, &filePos);
        CheckError (error, "\pGetFPos");
        
        error = SetEOF (dfRef, filePos);
        CheckError (error, "\pSetEOF");
        
        FSClose (dfRef);
        FlushVol (nil, fileReply.sfFile.vRefNum);

        MakeMyResource (fileReply, description);
        if (description != nil)
            DisposeHandle ((Handle) description);
    }
}

void MakeMyResource ( StandardFileReply fileReply,
                             ImageDescriptionHandle description)
{
    OSErr       error;
    short       rfRef;
    Handle      sequResource;

    FSpCreateResFile (&fileReply.sfFile, 'SEQM', 'SEQU',
                             fileReply.sfScript);

    error = ResError();
    if (error != dupFNErr)
        CheckError (error, "\pFSpCreateResFile");

    rfRef = FSpOpenResFile (&fileReply.sfFile, fsRdWrPerm);
    CheckError (ResError (), "\pFSpOpenResFile");

    SetResLoad (false);
    sequResource = Get1Resource ('SEQU', 128);
    if (sequResource)
        RmveResource (sequResource);
    SetResLoad (true);
    sequResource = (Handle) description;
    error = HandToHand (&sequResource);
    CheckError (error, "\pHandToHand");

    AddResource (sequResource,'SEQU', 128, "\p");
    CheckError (ResError (), "\pAddResource");
    UpdateResFile (rfRef);
    CheckError (ResError (), "\pUpdateResFile");

    CloseResFile (rfRef);
}

A Sample Function for Creating, Compressing, and Drawing a Sequence of Images

Listing 4 shows the CompressSequence function, which creates and then compresses the image sequence. CompressSequenceBegin informs the Image Compression Manager which compressor (of type codectype ) to use, what the desired compression quality is, the key frame rate, the portion of the image to compress (in this example, the entire image is compressed), and the image to be compressed (in this example, the pixel map [of type PixMap ] in the currWorld variable).

CompressSequenceBegin returns a unique number that identifies the sequence for subsequent image-compression routines, and it initializes a new image description structure, which is stored in the handle referenced by the description local variable.

Using a loop, the DrawOneFrame function draws each frame until the last frame is drawn, at which time the function returns the value of false . Each frame that it draws is copied to the window so that it can be seen during the compression sequence.

The CompressSequenceFrame function is used to compress each frame's image. CompressSequenceFrame tells the Image Compression Manager

In updating the previous frame's buffer for frame differencing, the Image Compression Manager control flag codecFlagUpdatePrevious copies the uncompressed image to the previous frame's buffer; contrast this with the codecFlagUpdatePreviousComp flag, which copies the compressed image to the previous frame's buffer--the more lossy the compression, the more the difference between the compressed and uncompressed images.

The CompressSequenceBegin function returns a rating of the similarity between the current frame and the previous frame, but this example ignores this rating. After each frame is compressed, the number of bytes in the compressed image data is written to the disk file, followed by the compressed image data itself.

After all the images in the sequence have been compressed, the CDSequenceEnd function is called to tell the Image Compression Manager that the sequence is over. The data fork of the file is closed, and the image description is written to a 'SEQU' resource.

The DrawOneFrame function draws one frame of the sequence with QuickDraw. The frame's image is drawn into the rectangle specified by the destRect parameter. The image is a set of color ramps in which the shading goes from light to dark in smooth increments. The color ramps fill the destination rectangle and the current frame number centered within the destination rectangle over the ramps.

The PaintImage function paints a series of vertical color ramps into the rectangle specified by the destRect parameter into the current color graphics port. This is done through a nested loop. The outer loop iterates only twice, and half of the ramps are drawn in the first iteration and half in the second. The inner loop iterates over all the steps in a ramp.

Compressing and decompressing a sequence of images: Drawing one frame with QuickDraw

void CompressSequence (short* dfRef,
                                    ImageDescriptionHandle* description)
{
    GWorldPtr           currWorld = nil;
    PixMapHandle        currPixMap;
    CGrafPtr            savedPort;
    GDHandle            savedDevice;
    Handle              buffer = nil;
    Ptr                 bufferAddr;
    long                compressedSize;
    long                dataLen;
    Rect                imageRect;
    ImageSequence       sequenceID = 0;
    short               frameNum;
    OSErr               error;
    CodecType           codecKind = 'rle ';
    
    GetGWorld (&savedPort, &savedDevice);
    imageRect = savedPort->portRect;
    error = NewGWorld (&currWorld, 32, &imageRect, nil, nil, 0);
    CheckError (error, "\pNewGWorld");
    SetGWorld (currWorld, nil);
    currPixMap = currWorld->portPixMap;
    LockPixels (currPixMap);

/*  
    Allocate an embryonic image description structure and the
    Image Compression Manager will resize.
*/
    *description = (ImageDescriptionHandle) NewHandle (4);
    
    error = CompressSequenceBegin (
            &sequenceID,
            currPixMap,
            nil,                        /* tell ICM to allocate previous
                                             image buffer */
            &imageRect,
            &imageRect,
            0,                          /* let ICM choose pixel depth */
            codecKind,
            (CompressorComponent) anyCodec,
            codecNormalQuality,         /* spatial quality */
            codecNormalQuality,         /* temporal quality */
            5,                          /* at least 1 key frame every
                                             5 frames */
            nil,                        /* use default color table */
            codecFlagUpdatePrevious,
            *description );
    CheckError (error, "\pCompressSequenceBegin");

    error = GetMaxCompressionSize(
            currPixMap,
            &imageRect,
            0,                              /* let ICM choose pixel depth */
            codecNormalQuality,             /* spatial quality */
            codecKind,
            (CompressorComponent) anyCodec,
            &compressedSize );
    CheckError (error, "\pGetMaxCompressionSize");

    buffer = NewHandle(compressedSize);
    CheckError (MemError(), "\pNewHandle buffer");
    MoveHHi (buffer);
    HLock (buffer);
    bufferAddr = StripAddress (*buffer);
    for (frameNum = 1; frameNum <= 10; frameNum++)
    {
        DrawFrame (&imageRect, frameNum);

        error = CompressSequenceFrame (
                        sequenceID,
                        currPixMap,
                        &imageRect,
                        codecFlagUpdatePrevious,
                        bufferAddr,
                        &compressedSize,
                        nil,
                        nil );
        CheckError (error, "\pCompressSequenceFrame");

        dataLen = 4;
        error = FSWrite (*dfRef, &dataLen, &compressedSize);
        CheckError (error, "\pFSWrite length");

        error = FSWrite (*dfRef, &compressedSize, bufferAddr);
        CheckError (error, "\pFSWrite buffer");
    }

    CDSequenceEnd (sequenceID);
    
    DisposeGWorld (currWorld);
    SetGWorld (savedPort,savedDevice);
    if (buffer) DisposeHandle ( buffer );
    }

void DrawFrame (const Rect *imageRect, long frameNum)
{
    Str255 numStr;

    ForeColor( redColor );
    PaintRect( imageRect );

    ForeColor( blueColor );
    NumToString (frameNum, numStr);
    MoveTo ( imageRect->right / 2, imageRect->bottom / 2);
    TextSize ( imageRect->bottom / 3);
    DrawString (numStr);
}

A Sample Function for Decompressing and Playing Back a Sequence From a Disk File

The SequencePlay function, shown in Listing 5 , plays back a sequence of images from a disk file that was created by the SequenceSave function (see Listing 3 for details).

The SequencePlay function begins by grabbing the image description structure from the file that the user specified from a 'SEQU' resource ID 128. This structure is needed to decompress the images in the file.

Before these compressed images are read, the Image Compression Manager is told to prepare to decompress a sequence of images through the DecompressSequenceBegin function. This routine tells the Image Compression Manager

A loop iterates for each frame in the file. For each frame, a long word with the number of bytes in the frame is read from the file, and then that many bytes are read from the file into a compressed-image buffer. This buffer is passed to DecompressSequenceFrame , which decompresses the image to the screen (the destination doesn't have to be the screen, but it is in this example). The loop iterates until the end of the file has been reached.

Compressing and decompressing a sequence of images: Decompressing and playing back a sequence from a disk file

void SequencePlay (void)
{
    ImageDescriptionHandle          description;
    long                        compressedSize;
    Handle                      buffer = nil;
    Ptr                         bufferAddr;
    long                 dataLen;
    long                        lastTicks;
    ImageSequence               sequenceID;
    Rect                        imageRect;
    StandardFileReply           fileReply;
    SFTypeList                  typeList = {'SEQU',0,0,0};
    short                       dfRef = 0;          /* sequence data fork */
    short                       rfRef = 0;          /* sequence resource fork */
    OSErr                       error;
    StandardGetFile (nil, 1, typeList, &fileReply);
    if (!fileReply.sfGood) return;
    
    rfRef = FSpOpenResFile (&fileReply.sfFile, fsRdPerm);
    CheckError (ResError (), "\pFSpOpenResFile");

    description = (ImageDescriptionHandle)
                                Get1Resource ('SEQU', 128);
    CheckError (ResError (), "\pGet1Resource");

    DetachResource ((Handle) description );
    HNoPurge ((Handle) description );
    CloseResFile (rfRef);

    error = FSpOpenDF (&fileReply.sfFile, fsRdPerm, &dfRef);
    CheckError (error, "\pFSpOpenDF");

    buffer = NewHandle (4);
    CheckError (MemError (), "\pNewHandle buffer");

    SetRect (&imageRect, 0, 0, (**description).width,
                                         (**description).height);
    error = DecompressSequenceBegin (
                        &sequenceID,
                        description,
                        nil,                        /* use the current port */
                        nil,                        /* go to screen */
                        &imageRect,
                        nil,                        /* no matrix */
                        ditherCopy,
                        nil,                        /* no mask region */
                        codecFlagUseImageBuffer,
                        codecNormalQuality,         /* accuracy */
                        (CompressorComponent) anyCodec);

    while (true)
    {
        dataLen = 4;
        error = FSRead (dfRef, &dataLen, &compressedSize);
        if (error == eofErr)
            break;
        CheckError( error, "\pFSRead" );
        if (compressedSize > GetHandleSize (buffer))
        {
            HUnlock (buffer);
            SetHandleSize (buffer, compressedSize);
            CheckError (MemError(), "\pSetHandleSize");
        }

        HLock (buffer);
        bufferAddr = StripAddress (*buffer);
        error = FSRead (dfRef, &compressedSize, bufferAddr);
        CheckError (error, "\pFSRead");
        
        error = DecompressSequenceFrame (
                                sequenceID,
                                bufferAddr,
                                0,  // flags
                                nil,
                                nil );
        CheckError (error, "\pDecompressSequenceFrame");

        Delay (30, &lastTicks);
    }
    
    CDSequenceEnd (sequenceID);
    if (dfRef) FSClose (dfRef);
    if (buffer) DisposeHandle (buffer);
    if (description) DisposeHandle ((Handle)description);
}

© 1999 Apple Computer, Inc.

Previous | Overview | Contents | Next